// ....................................................................................................................
//	Author: Copyright (C) 2012 Ing. Marek Hubal
//	Email: hubal@acasoftware.org
// ....................................................................................................................

#include <Windows.h>
#include "ICDDriveReader.h"

namespace core2namespace {

using namespace Microsoft::Win32::SafeHandles;

// ....................................................................................................................
// Konstruktor
// Vstup:
//			ziaden
// Vystup:
//			ziaden
// ....................................................................................................................
ICDDriveReaded::ICDDriveReaded(void) {
	FHandle = NULL;
	FTracks = gcnew System::Collections::Generic::List<ITrackItem^>(); // Alokujem pole
	FReadDataStream = nullptr;
}
// ....................................................................................................................
// ....................................................................................................................
// Destruktor
// Vstup:
//			ziaden
// Vystup:
//			ziaden
// ....................................................................................................................
ICDDriveReaded::~ICDDriveReaded(void) {
	Close(); // Volam zatvorenie
	if (FTracks != nullptr) delete FTracks;
	FTracks = nullptr;
	FReadBuffer = NULL; FReadInfo = NULL;
}
// ....................................................................................................................
// ....................................................................................................................
// Uzatvorenie objektu
// Vstup:
//			ziaden
// Vystup:
//			ziaden
// ....................................................................................................................
void ICDDriveReaded::Close(void) {
	if (FHandle == NULL) return; // Uz je zatvorene?
	::CloseHandle(FHandle); // Zatvorim
	FHandle = NULL;
	if (FReadBuffer != NULL) delete FReadBuffer;
	FReadBuffer = NULL;
	if (FReadInfo != NULL) delete FReadInfo;
	FReadInfo = NULL;
	if (FReadDataStream != nullptr) delete FReadDataStream;
	FReadDataStream = nullptr;

	FTracks->Clear(); // Vymazem zoznam trackov
}
// ....................................................................................................................
// ....................................................................................................................
// Otvorenie objektu
// Vstup:
//			pDriveLetter - znak zariadenia
// Vystup:
//			0 alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::Open(wchar_t pDriveLetter) {
	Close(); // Volam najpr uzatvorenie

	char FN[8] = { '\\', '\\', '.', '\\', (char)pDriveLetter, ':', '\0' };
	FHandle = ::CreateFile(FN, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); // Otvorim zariadenie na citanie
	if (FHandle == INVALID_HANDLE_VALUE) {
		FHandle = NULL;
		return -10;
	}
	return 0;
}
// ....................................................................................................................
// ....................................................................................................................
// Vysunutie CD
// Vstup:
//			ziaden
// Vystup:
//			0 alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::EjectCD(void) {
	if (FHandle == NULL) return -7; // Nie je otvorene zariadenie?

	DWORD I;
	if (DeviceIoControl(FHandle, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &I, NULL) != 0) return 0; // Zavolam otvorenie dvierok
	return -10;
}
// ....................................................................................................................
// ....................................................................................................................
// Zasunutie CD
// Vstup:
//			ziaden
// Vystup:
//			0 alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::InsertCD(void) {
	if (FHandle == NULL) return -7; // Nie je otvorene zariadenie?

	DWORD I;
	if (DeviceIoControl(FHandle, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &I, NULL) != 0) return 0; // Zavolam zasunutie dvierok
	return -10;
}
// ....................................................................................................................
// ....................................................................................................................
// Test pripravenosti CD mechaniky
// Vstup:
//			ziaden
// Vystup:
//			0 nie je, 1 - je pripravena alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::IsCDReady(void) {
	if (FHandle == NULL) return -7; // Nie je otvorene zariadenie?
	DWORD I;
	return DeviceIoControl(FHandle, IOCTL_STORAGE_CHECK_VERIFY2, NULL, 0, NULL, 0, &I, NULL) != 0 ? 1 : 0; // Zavolam test pripravenosti
}
// ....................................................................................................................
// ....................................................................................................................
// Uzamknutie zariadenia
// Vstup:
//			ziaden
// Vystup:
//			0 alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::LockCD(void) {
	if (FHandle == NULL) return -7; // Nie je otvorene zariadenie?

	DWORD I;
	PREVENT_MEDIA_REMOVAL pmr = { TRUE };

	if (DeviceIoControl(FHandle, IOCTL_STORAGE_MEDIA_REMOVAL, &pmr, sizeof(pmr), NULL, 0, &I, NULL) != 0) return 0; // Zavolam uzamknutie
	return -10;
}
// ....................................................................................................................
// ....................................................................................................................
// Odomknutie zariadenia
// Vstup:
//			ziaden
// Vystup:
//			0 alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::UnlockCD(void) {
	if (FHandle == NULL) return -7; // Nie je otvorene zariadenie?

	DWORD I;
	PREVENT_MEDIA_REMOVAL pmr = { FALSE };

	if (DeviceIoControl(FHandle, IOCTL_STORAGE_MEDIA_REMOVAL, &pmr, sizeof(PREVENT_MEDIA_REMOVAL), NULL, 0, &I, NULL) != 0) return 0; // Zavolam uzamknutie
	return -10;
}
// ....................................................................................................................
// ....................................................................................................................
// Nacitanie zoznamu stop
// Vstup:
//			ziaden
// Vystup:
//			0 alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::ReadTrackList(void) {
	int R = LockCD(); // Uzamknem mechaniku
	if (R != 0) return R;

	DWORD I;
	CDROM_TOC Table;
	// Citam zoznam stop...
	if (DeviceIoControl(FHandle, IOCTL_CDROM_READ_TOC, NULL, 0, &Table, sizeof(Table), &I, NULL) == 0)
	{
		UnlockCD(); // Uvolnim mechaniku
		return -10;
	}
	for (int i = Table.FirstTrack - 1; i < Table.LastTrack; i++)
	{
		ITrackItem ^Item = gcnew ITrackItem();
		Item->TrackName = String::Format(DefaultTrackName, i + 1);
		Item->TOC_Address_1 = Table.TrackData[i].Address[0]; Item->TOC_Address_2 = Table.TrackData[i].Address[1]; Item->TOC_Address_3 = Table.TrackData[i].Address[2]; Item->TOC_Address_4 = Table.TrackData[i].Address[3];
		Item->TrackAddress = TranslateAddressToSectors(Table.TrackData[i].Address);
		Item->TrackLength = TranslateAddressToSectors(Table.TrackData[i + 1].Address) - Item->TrackAddress;
		Item->TrackLengthInTime = GetTrackLengthInTime(Table.TrackData[i].Address, Table.TrackData[i + 1].Address);
		Item->TrackType = Table.TrackData[i].Control;
		FTracks->Add(Item); // Zaradim do zoznamu
	}
	return 0; // Vsetko OK
}
// ....................................................................................................................
// ....................................................................................................................
// Prevod adresy na sektory
// Vstup:
//			Addr - udaj pre prepocet
// Vystup:
//			poradove cislo sektora
// ....................................................................................................................
unsigned __int64 ICDDriveReaded::TranslateAddressToSectors(unsigned char Addr[4]) {
	unsigned __int64 Sectors = 0;
	Sectors += Addr[1] * (CD_BLOCKS_PER_SECOND * 60);
	Sectors += Addr[2] * CD_BLOCKS_PER_SECOND;
	Sectors += Addr[3];
	return Sectors - 150;
}
// ....................................................................................................................
// ....................................................................................................................
// Property: TracksCount - get
// Vstup:
//			ziaden
// Vystup:
//			pocet stop
// ....................................................................................................................
int ICDDriveReaded::TracksCount::get(void) {
	return FTracks->Count;
}
// ....................................................................................................................
// ....................................................................................................................
// Property: TracksCount - get
// Vstup:
//			pIndex - pozicia stopy
// Vystup:
//			objekt stopy alebo nullptr
// ....................................................................................................................
ITrackItem^ ICDDriveReaded::default::get(int pIndex) {
	return (pIndex < 0) || (pIndex >= FTracks->Count) ? nullptr : FTracks[pIndex];
}
// ....................................................................................................................
// ....................................................................................................................
// Vypocet dlzky stopy v milisekundach
// Vstup:
//			Addr1 - zaciatok stopy
//			Addr2 - koniec stopy
// Vystup:
//			dlzka stopy v milisekundach
// ....................................................................................................................
int ICDDriveReaded::GetTrackLengthInTime(unsigned char Addr1[4], unsigned char Addr2[4]) {
	unsigned __int64 I1 = TranslateAddressToSectors(Addr1);
	unsigned __int64 I2 = TranslateAddressToSectors(Addr2);
	I2 -= I1; I2 *= RAW_SECTOR_SIZE; // Dlzka v bajtoch
	return (int)((double)I2 / 176.4);
}
// ....................................................................................................................
// ....................................................................................................................
// Otvorenie stopy pre citanie audia
// Vstup:
//			pozicia stopy
// Vystup:
//			0 alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::OpenAudioTrackForRead(int pIndex) {
	if (FHandle == NULL) return -7; // Nie je otvorene zariadenie?
	if (FReadBuffer != NULL) delete FReadBuffer; // Zrusim buffer
	FReadBuffer = new unsigned char[MAX_READ_BUFFER_SIZE]; // Alokujem buffer
	
	ReadBufferLength = MAX_READ_BUFFER_SIZE;
	FTotalSectorsToRead = (int)FTracks[pIndex]->TrackLength;
	FReadedSectors = 0;
	FReadInfo = new RAW_READ_INFO;
	FReadInfo->TrackMode = CDDA;
	FReadInfo->SectorCount = SECTORS_AT_READ;
	FStartReadSector = FTracks[pIndex]->TrackAddress;

	return LockCD();
}
// ....................................................................................................................
// ....................................................................................................................
// Uzatvorenie stopy pre citanie audia
// Vstup:
//			ziaden
// Vystup:
//			ziaden
// ....................................................................................................................
void ICDDriveReaded::CloseAudioTrackForRead(void) {
	UnlockCD(); 
	if (FReadBuffer != NULL) delete FReadBuffer; // Zrusim buffer
	FReadBuffer = NULL;
	if (FReadInfo != NULL) delete FReadInfo;
	FReadInfo = NULL;
}
// ....................................................................................................................
// ....................................................................................................................
// Citanie z audio stopy
// Vstup:
//			Result - vysledny buffer
//			pStart - zaciatok zapisu do bufra
//			pMaxLength - maximalny pocet nacitanych bajtov
// Vystup:
//			pocet nacitanych bajtov alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::ReadAudioTrack(array<unsigned char> ^Result, int pStart, int pMaxLength) {
	if (FReadedSectors == FTotalSectorsToRead) return 0; // Koniec?
	int C = FTotalSectorsToRead - FReadedSectors;
	if (C > SECTORS_AT_READ) C = SECTORS_AT_READ;
	FReadInfo->SectorCount = C;
	FReadInfo->DiskOffset.QuadPart = (FStartReadSector + FReadedSectors) * CD_SECTOR_SIZE;

	ULONG I;
	if (DeviceIoControl(FHandle, IOCTL_CDROM_RAW_READ, FReadInfo, sizeof(RAW_READ_INFO), FReadBuffer, C * RAW_SECTOR_SIZE, &I, NULL) == 0) {
		int I = ::GetLastError();
		return -17;
	}

	int CC = C * RAW_SECTOR_SIZE;
	for (int i = 0; i < CC; i++) Result[i] = FReadBuffer[i];

	FReadedSectors += C;
	return C * RAW_SECTOR_SIZE;
}
// ....................................................................................................................
// ....................................................................................................................
// Otvorenie stopy pre citanie dat
// Vstup:
//			pozicia stopy
// Vystup:
//			0 alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::OpenDataTrackForRead(int pIndex) {
	if (FHandle == NULL) return -7; // Nie je otvorene zariadenie?
	try 
	{
		ReadBufferLength = 0x20000;
		FReadDataStream = gcnew System::IO::FileStream(gcnew SafeFileHandle((IntPtr)FHandle, false), System::IO::FileAccess::Read, ReadBufferLength);
	}
	catch(...)
	{
		return -17;
	}

	return LockCD();
}
// ....................................................................................................................
// ....................................................................................................................
// Uzatvorenie stopy pre citanie dat
// Vstup:
//			ziaden
// Vystup:
//			ziaden
// ....................................................................................................................
void ICDDriveReaded::CloseDataTrackForRead(void) {
	UnlockCD(); 
	if (FReadDataStream != nullptr) delete FReadDataStream;
	FReadDataStream = nullptr;
}
// ....................................................................................................................
// ....................................................................................................................
// Citanie z datovej stopy
// Vstup:
//			Result - vysledny buffer
//			pStart - zaciatok zapisu do bufra
//			pMaxLength - maximalny pocet nacitanych bajtov
// Vystup:
//			pocet nacitanych bajtov alebo kod chyby
// ....................................................................................................................
int ICDDriveReaded::ReadDataTrack(array<unsigned char> ^Result, int pStart, int pMaxLength) {
	try 
	{
		return FReadDataStream->Read(Result, pStart, pMaxLength);
	}
	catch(...)
	{
		return -17;
	}
}
// ....................................................................................................................

}
